/*
 * FileAsMsg plug-in for Miranda IM and QIP Infium.
 * Sending/receiving files via instant messaging without protocol support.
 * Homepage: http://code.google.com/p/fileasmsg/
 *
 * Copyright (c) 2008, Radist <radist.nt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Name(s) of contributor(s) may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Radist ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Radist BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "workthread.h"
#include "threads.h"
#include "Sessions.h"
#include "proto.h"
#include <CommCtrl.h>
#include <memory>
#include <malloc.h>
#include "plugin.h"

#define TranslateW_nolpgen TranslateW

typedef struct
{
	SLIST_ENTRY slist;
	enum {
		MSG_CREATE_RECV = 0xCF3501,
		MSG_CREATE_SEND,
		MSG_DELETE = 0xCF3700,
	} msg;
	HANDLE hContact;
	DWORD flags;
	DWORD filesize;
	DWORD chunk_size;
	wchar_t filename[1];
} WT_TRANSFERER_PARAMS;

static __declspec(align(MEMORY_ALLOCATION_ALIGNMENT)) SLIST_HEADER msg_list;

static VOID CALLBACK WorkThreadTimerDispatcher(HWND wnd, UINT, UINT_PTR tmr, DWORD)
{
	KillTimer(wnd, tmr);
	Sessions &allsess = Sessions::GetInstance();
	for (WT_TRANSFERER_PARAMS *_par = (WT_TRANSFERER_PARAMS*)InterlockedFlushSList(&msg_list), *params;
		params = _par; _par = (WT_TRANSFERER_PARAMS*)_par->slist.Next, _aligned_free(params))
	{
		TransferSession *sess;
		switch (params->msg)
		{
		case WT_TRANSFERER_PARAMS::MSG_CREATE_RECV:
			if (!params->filesize||!params->chunk_size)
			{
				continue;
			}
		case WT_TRANSFERER_PARAMS::MSG_CREATE_SEND:
			if (!params->hContact||INVALID_HANDLE_VALUE==params->hContact)
			{
				continue;
			}
			if (!params->filename)
			{
				continue;
			}
			if (!allsess.Get(params->hContact))
			{
				try
				{
					sess = params->filesize ?
						(TransferSession*)new RecvSession(params->hContact, params->filename, params->flags, params->filesize, params->chunk_size) :
						(TransferSession*)new SendSession(params->hContact, params->filename);
				}
				catch(const wchar_t*ch)
				{
					MessageBoxW(NULL, TranslateW_nolpgen(ch),
						TranslateW(L"Error"), MB_ICONERROR|MB_OK);
					continue;
				}
				allsess.Add(params->hContact, sess);
			}
			break;
		case WT_TRANSFERER_PARAMS::MSG_DELETE:
			if (sess = allsess.Delete((HANDLE)params->hContact))
			{
				Sleep(200);//to end everything inside *sess object
				delete sess;
			}
			continue;
		}
	}
}

static unsigned WINAPI WorkThreadFunc( void *evparam )
{
	{
		INITCOMMONCONTROLSEX icc = {0};
		icc.dwICC = sizeof(icc);
		icc.dwICC = ICC_PROGRESS_CLASS;
		InitCommonControlsEx(&icc);
	}

	MSG msg;
	PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
	((Event*)evparam)->Set();
	evparam = NULL;
	//Message loop
	while (GetMessageW(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessageW(&msg);
	}
	Sessions::GetInstance().FreeAllSessions();
	return 0;
}

static HANDLE wthread = NULL;
static DWORD wthreadi = NULL;

void StartWorkThread( void )
{
	if (wthread) return;
	Event ev;
	InitializeSListHead(&msg_list);
	wthread = ForkThread(WorkThreadFunc, (void*)&ev, (unsigned int*)&wthreadi);
	if (INVALID_HANDLE_VALUE==wthread||!wthread)
	{
		wthread = NULL;
		return;
	}
	ev.WaitFor(INFINITE);
	Sleep(100);
}

void EndWorkThread( bool _wait /*= false*/ )
{
	if (!wthread) return;
	PostThreadMessageW(wthreadi, WM_QUIT, 0, 0);
	if (_wait)
	{
		WaitForSingleObject(wthread, INFINITE);
		CloseHandle(wthread);
	}
	wthread = NULL;
	while (PSLIST_ENTRY entry = InterlockedPopEntrySList(&msg_list))
	{
		_aligned_free(entry);
	}
}

void CreateTransferer( HANDLE hContact, wchar_t *filename, DWORD flags /*= 0*/, DWORD filesize /*= 0*/, DWORD chunk_size /*= 0*/ )
{
	if (!hContact||INVALID_HANDLE_VALUE==hContact||!filename)
	{
		return;
	}
	WT_TRANSFERER_PARAMS *params = (WT_TRANSFERER_PARAMS*)_aligned_malloc(sizeof(WT_TRANSFERER_PARAMS) + (wcslen(filename))*sizeof(wchar_t), MEMORY_ALLOCATION_ALIGNMENT);
	params->msg = filesize ? WT_TRANSFERER_PARAMS::MSG_CREATE_RECV : WT_TRANSFERER_PARAMS::MSG_CREATE_SEND;
	params->hContact = hContact;
	params->flags = flags;
	params->filesize = filesize;
	params->chunk_size = chunk_size;
#pragma warning(suppress:4996)
	wcscpy(params->filename, filename);
	InterlockedPushEntrySList(&msg_list, (PSINGLE_LIST_ENTRY)params);
	PostThreadMessageW(wthreadi, WM_TIMER, SetTimer(NULL, 0, INT_MAX, &WorkThreadTimerDispatcher), (LPARAM)&WorkThreadTimerDispatcher);
}

void NBDestroyTransferer( HANDLE hContact )
{
	if (!hContact||INVALID_HANDLE_VALUE==hContact)
	{
		return;
	}
	WT_TRANSFERER_PARAMS *params = (WT_TRANSFERER_PARAMS*)_aligned_malloc(offsetof(WT_TRANSFERER_PARAMS, flags), MEMORY_ALLOCATION_ALIGNMENT);
	params->msg = WT_TRANSFERER_PARAMS::MSG_DELETE;
	params->hContact = hContact;
	InterlockedPushEntrySList(&msg_list, (PSINGLE_LIST_ENTRY)params);
	PostThreadMessageW(wthreadi, WM_TIMER, SetTimer(NULL, 0, INT_MAX, &WorkThreadTimerDispatcher), (LPARAM)&WorkThreadTimerDispatcher);
}

